#include "ET++.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "StyledText.h"

#include "Class.h"
#include "WindowSystem.h"
#include "RunArray.h"
#include "Mark.h"
#include "OrdColl.h"
#include "Error.h"
#include "Invariant.h"
#include "TextReader.h"
#include "Math.h"
#include "ET_stdio.h"
#include "String.h"

InvariantChecker(StyledText);

//----- StyledText -------------------------------------------------------------

NewMetaImpl(StyledText,GapText, (TP(charStyles), TP(paraStyles),
					TP(styleHere), T(applyStyleHere)));

static TextChanges changeRec;

StyledText::StyledText(int sz, Font *fd, TxtParaAlign al) : GapText(sz, fd)
{
	InitStyles(new_CharStyle(fd), new_ParaStyle(al));
}

StyledText::StyledText(byte *buf, int len, Font *fd, TxtParaAlign al)
													: GapText(buf, len, fd)
{
	InitStyles(new_CharStyle(fd), new_ParaStyle(al));
}

StyledText::StyledText(Font *fd, const char *va_(fmt), ...)
{
	char *buf;

	va_list ap;
	va_start(ap,va_(fmt));
	buf= strvprintf(va_(fmt), ap);
	va_end(ap);

	InitStyles(new_CharStyle(fd), new_ParaStyle());
	BuildStylesFromString(new_CharStyle(fd), buf);
	SafeDelete(buf);
}

StyledText::~StyledText()
{
	SafeDelete(charStyles);
	SafeDelete(paraStyles);
}

void StyledText::InitNew()
{
	GapText::InitNew();
	InitStyles(new_CharStyle(gSysFont), new_ParaStyle());
}

void StyledText::InitStyles(CharStyle *cs, ParaStyle *ps)
{
	charStyles= new RunArray;
	paraStyles= new RunArray;
	applyStyleHere= FALSE;
		
	paraStyles->Insert(ps, 0, 0, Size());
	charStyles->Insert(cs, 0, 0, Size());
}

int StyledText::GetNextFontChange(int at, CharStyle *&sp)
{
	int start, end, sz, lenat;
	at= Math::Range(0, Size()-1, at);
	sp= (CharStyle*)charStyles->RunAt(at, &start, &end, &sz, &lenat);
	return at+lenat;
}

void StyledText::ReplaceWithStr(byte *str,int len)
{
	GapText::ReplaceWithStr(str, len);
	SafeDelete(charStyles);
	SafeDelete(paraStyles);
	InitStyles(new_CharStyle(GetFont()), new_ParaStyle());
}

void StyledText::Cut(int from, int to)
{
	int start, end, sz, lenat;
	CharStyle *sp= (CharStyle*)charStyles->RunAt(from, &start, &end, &sz, &lenat);
	applyStyleHere= FALSE;
	if (start == from && sz <= to - from) {
		applyStyleHere= TRUE;
		styleHere= sp;
	}
	GapText::Cut(from, to);
}

void StyledText::Dump()
{
	RunArrayIter next(paraStyles);
	int start, end, sz, i;

	fprintf(stderr, "DUMP\n");
	fprintf(stderr, "Runs\n");
	for (i= 0; next.Run(&start, &end, &sz) != 0; i++) {
		fprintf(stderr, "%d ", end);
		if (!IsBreak(CharAt(end-1)))
			fprintf(stderr, "!");
	}
	fprintf(stderr, "\n");
}

void StyledText::CheckInvariant()
{
	if (GetAssertLevel() > 10)
		return;

	RunArrayIter next(paraStyles);
	int start, end, sz, i;

	if (paraStyles->Size() != Size()) {
		fprintf(stderr, "!!!StyledText Invariant: paraStyles: size differs! %d %d\n", paraStyles->Size(), Size());
		Dump();
	}
	if (charStyles->Size() != Size()) {
		fprintf(stderr, "!!!StyledText Invariant: charStyles: size differs! %d %d\n", paraStyles->Size(), Size());
		Dump();
	}
	for (i= 0; next.Run(&start, &end, &sz) != 0; i++) {
		if (!IsBreak(CharAt(end-1)) && end < Size()) {
			fprintf(stderr, "!!!StyledText Invariant: no paragraph at %d\n", end-1);
			Dump();
		}
	}
}

void StyledText::CutParaStyles(int from, int to, int sz)
{
	int pfl, pfr;

	GetParaRange(from, &pfl, &pfr);
	paraStyles->Cut(from, to);
	paraStyles->ChangeRunSize(from, sz, eRARight);
	if (pfl < Size()) {
		ParaStyle *ps= GetParaStyle(pfl);
		paraStyles->Insert(ps, from, pfr, pfr-from);
	}
}

void StyledText::ReplaceRange(int from, int to, Text* src, int sfrom, int sto)
{
	AssertInvariant(StyledText);
	int sz= sto-sfrom;

	DoDelayChanges dc(this);
	dc.Use();

	GapText::ReplaceRange(from, to, src, sfrom, sto);
	if (src->IsKindOf(StyledText)) {
		StyledText *st= (StyledText*) src;
		charStyles->ReplaceRange(from, to, st->charStyles, sfrom, sto);
		PasteParaStyles(from, to, st->paraStyles, sfrom, sto);
		return;
	}
	if (from != to)
		CutStyles(from, to, sz);
	else
		paraStyles->ChangeRunSize(from, sz, eRARight);
		
	if (applyStyleHere && sz) { // insert new run with styleHere
		charStyles->Insert(styleHere, from, from, sz);
		applyStyleHere= FALSE;
	} else
		AddChars(from, sz);
}

void StyledText::CutStyles(int from, int to, int sz)
{
	int start, end, siz, lenat;
	CharStyle *sp= (CharStyle*)charStyles->RunAt(from, &start, &end, &siz, &lenat);
	if (start == from && siz <= to - from) {
		applyStyleHere= TRUE;
		styleHere= sp;
	}
	CutParaStyles(from, to, sz);
	charStyles->Cut(from, to);
}

void StyledText::PasteParaStyles(int from, int to, RunArray *pr, int sfrom, int sto)
{
	int pfl, pfr, ptl, ptr, sz= sto-sfrom;
	ParaStyle *ps;

	paraStyles->ReplaceRange(from, to, pr, sfrom, sto);

	GetParaRange(from, &pfl, &pfr);
	GetParaRange(from+sz, &ptl, &ptr);

	if(pfl == ptl) {
		ps= GetParaStyle(from+sz);
		paraStyles->Insert(ps, from, from+sz, sz);
	} else {
		ps= GetParaStyle(pfl);
		paraStyles->Insert(ps, from, pfr, pfr-from);
		ps= GetParaStyle(ptl);
		paraStyles->Insert(ps, from+sz, ptr, ptr-from-sz);
	}
}

void StyledText::AddChars(int at, int sz)
{
	RArrayGrowDir g= eRALeft;
	if (IsParaStart(at))
		g= eRARight;
	charStyles->ChangeRunSize(at, sz, g);
}

void StyledText::SetFStringVL(char *fmt, va_list ap)
{
	char *buf= strvprintf(fmt, ap);

	CharStyle *st= GetCharStyle(0);
	BuildStylesFromString(st, buf);
	SafeDelete(buf);
}

OStream &StyledText::PrintOn(OStream &s)
{
	AssertInvariant(StyledText);
	GapText::PrintOn(s);
	return s << charStyles SP << paraStyles SP;
}

IStream &StyledText::ReadFrom(IStream &s)
{
	AssertInvariant(StyledText);
	GapText::ReadFrom(s);
	s >> charStyles >> paraStyles;
	return s;
}

IStream& StyledText::ReadFromAsPureText(IStream &s, long sizeHint)
{
	GapText::ReadFromAsPureText(s, sizeHint);
	SafeDelete(paraStyles);
	SafeDelete(charStyles);
	InitStyles(new_CharStyle(GapText::GetFont(0)), new_ParaStyle());
	return s;
}

void StyledText::SetCharStyle(TxtCharProp what, int from, int to, const CharStyleSpec &st)
{
	int start, end, sz;

	if (from == to) {
		CharStyle *sp= applyStyleHere ? styleHere : GetCharStyle(Math::Max(0, from-1));
		applyStyleHere= TRUE;
		styleHere= gCharStyles->ChangeProperty(sp, what, st);
	} else {
		CharStyle **spp;
		RunArray newStyles(2);
		charStyles->Copy(&newStyles, from, to);
		RunArrayIter next(&newStyles);
		while (spp= (CharStyle**)next.RunPtr(&start, &end, &sz))
			*spp= gCharStyles->ChangeProperty(*spp, what, st);
		charStyles->Paste(&newStyles, from, to);
		
		GetMarkList()->RangeChanged(from, to - from);
		Send(cIdNone, eTextChangedRange, changeRec(from, to));
	}
}

void StyledText::BuildStylesFromString(CharStyle *cs, char *buf)
{
	register char *p= buf;
	register int c;
	TextReader *reader= new TextReader(this, cs, new_ParaStyle());

	while (c= *p++) {
		if (c == '@') {
			switch (c= *p++) {
			case 0:
				break;
			case 'P':
				reader->SetFace(eFacePlain);
				break;
			case 'O':
				reader->ToggleFace(eFaceOutline);
				break;
			case 'S':
				reader->ToggleFace(eFaceShadow);
				break;
			case 'U':
				reader->ToggleFace(eFaceUnderline);
				break;
			case 'B':
				reader->ToggleFace(eFaceBold);
				break;
			case 'I':
				reader->ToggleFace(eFaceItalic);
				break;
			case 'x':
				reader->SetInk(gInkBlack);
				break;
			case 'r':
				reader->SetInk(gInkRed);
				break;
			case 'g':
				reader->SetInk(gInkGreen);
				break;
			case 'b':
				reader->SetInk(gInkBlue);
				break;
			case '+':
				reader->SetSize(reader->GetSize()+4);
				break;
			case '-':
				reader->SetSize(reader->GetSize()-4);
				break;
			case '[':
				reader->SetParaProp(eTxtPAlign, eTxtParaLeft);
				break;
			case ']':
				reader->SetParaProp(eTxtPAlign, eTxtParaRight);
				break;
			case '|':
				reader->SetParaProp(eTxtPAlign, eTxtParaCenter);
				break;
			case '_':
				reader->SetParaProp(eTxtPAlign, eTxtParaJustified);
				break;
			case '<':
				reader->SetParaProp(eTxtPLeft, 0);
				break;
			case '>':
				reader->SetParaProp(eTxtPLeft, 20);
				break;
			case '^':
				reader->SetParaProp(eTxtPSpaceBefore, 20);
				break;
			case 'v':
				reader->SetParaProp(eTxtPSpaceBefore, 0);
				break;
			default:
				reader->Append(c);
				break;
			}
		} else
			reader->Append(c);
	}
	reader->Flush();
	SafeDelete(reader);
	CheckInvariant();
}

CharStyle *StyledText::GetCharStyle(int at)
{
	int d; // ???? -1
	at= Math::Range(0, Size()-1, at);
	return (CharStyle*) charStyles->RunAt(at, &d, &d, &d, &d);
}

void StyledText::ReplaceCharStyles(RunArray *st, int from, int to)
{
	if (to-from != st->Size())
		Error("ReplaceCharStyle", "RunArray does not match text");
	charStyles->Paste(st, from, to);

	GetMarkList()->RangeChanged(from, to-from);
	Send(cIdNone, eTextChangedRange, changeRec(from, to));
}

void StyledText::ReplaceParaStyles(RunArray *st, int from, int to)
{
	if (to-from != st->Size())
		Error("ReplaceParaStyles", "RunArray does not match text");
	paraStyles->Paste(st, from, to);

	GetMarkList()->RangeChanged(from, to-from);
	Send(cIdNone, eTextChangedRange, changeRec(from, to));
}

void StyledText::ResetCurrentCharStyle()
{
	if (End() != 0)
		applyStyleHere= FALSE;
}

CharStyle *StyledText::GetCurrentCharStyle()
{
	if (applyStyleHere)
		return styleHere;
	return 0;
}

RunArray *StyledText::GetCharStyles()
{
	return charStyles;
}

RunArray *StyledText::GetParaStyles()
{
	return paraStyles;
}

RunArray *StyledText::SetCharStyles(RunArray *st)
{
	if (st->Size() != Size())
		Error("StyledText", "StyleArray does not correspond to the text");

	RunArray *old= charStyles;
	charStyles= st;
	return old;
}

RunArray *StyledText::SetParaStyles(RunArray *st)
{
	if (st->Size() != Size())
		Error("StyledText", "StyleArray does not correspond to the text");

	RunArray *old= paraStyles;
	paraStyles= st;
	return old;
}

void StyledText::SetParaStyle(TxtParaProp what, int from, int to, const ParaDesc &pd)
{
	int pfl, ptr, dd;   // paragraph ranges
	int start, end, sz;

	// find start and end of paragraphs
	GetParaRange(from, &pfl, &dd);
	if (to == from)
		ptr= dd;
	else if (IsPara((*this)[to-1]))
		ptr= to;
	else
		GetParaRange(to, &dd, &ptr);

	ParaStyle **spp;
	RunArray newStyles(2);
	paraStyles->Copy(&newStyles, pfl, ptr);
	RunArrayIter next(&newStyles);
	while (spp= (ParaStyle**)next.RunPtr(&start, &end, &sz))
		*spp= gParaStyles->ChangeProperty(*spp, what, pd);
	paraStyles->Paste(&newStyles, pfl, ptr);

	GetMarkList()->RangeChanged(pfl, ptr-pfl);
	bool pagination= ((what & eTxtPNoBreak) == eTxtPNoBreak)
					|| ((what & eTxtPKeepNext) == eTxtPKeepNext);
	Send(cIdNone, eTextChangedRange, changeRec(pfl, ptr, 0, pagination));
}

ParaStyle *StyledText::GetParaStyle(int at)
{
	int d;
	at= Math::Range(0, Size()-1, at);
	return (ParaStyle*) paraStyles->RunAt(at, &d, &d, &d, &d);
}

